/*
 * Copyright (c) 2010, artur
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * kiddie-os
 * Created on: 14.04.2010
 *     Author: artur
 *
 */

#include <kiddie/kiddie.h>
#include <Console.h>
#include <hw/Platform.h>
#include <sys/sched/Scheduler.h>
#include <arch/asm-syscalls.h>
#include "PciEnumerator.h"
#include <kiddie/IRunnable.h>

extern kernel::Console cout;
extern kernel::Platform hwplatform;
extern kernel::sys::sched::Scheduler *scheduler;

static struct pci_dev pci_dev_list[MAX_PCI_DEV];

#if 0
static void run_ixgbe(void *vaddr)
  {
    int i,j;
    struct pci_dev *pdev;

    for (i=0;i<MAX_PCI_DEV;i++)
      {
        pdev = &pci_dev_list[i];
        if (!pdev) continue;
        if (pdev->r0.struct_r0.vendor_id == PCI_INTEL_ID &&
            pdev->r0.struct_r0.dev_id == IXGBE_DEV_ID_82598AF_DUAL_PORT)
          {
            ::cout << "[ PciEnumerator ] Found IXGBE Device!\r\n";
            ::cout << kernel::Console::HEX;

            for (j = 0; j < PCI_CONF_BAR_MAX; j++)
              {
                ::cout << "     BAR=0x";
                ::cout << (int)pdev->bar[j];
                ::cout << "\n";
              }
          }

        if (pdev->r0.struct_r0.vendor_id == PCI_INTEL_ID &&
            pdev->r0.struct_r0.dev_id == 0x100f)
          {
            ::cout << "[ PciEnumerator ] Found 82545EM Gigabit Ethernet Controller!\r\n";
            ::cout << kernel::Console::HEX;

            for (j = 0; j < PCI_CONF_BAR_MAX; j++)
              {
                ::cout << "     BAR=0x";
                ::cout << (int)pdev->bar[j];
                ::cout << "\n";
              }
          }

        /* Pcnet32 device */
        if (pdev->r0.struct_r0.vendor_id == 0x1022 &&
            pdev->r0.struct_r0.dev_id == 0x2000)
          {
            ::cout << "[ PciEnumerator ] Found PCNet32 Device!\r\n";
            ::cout << kernel::Console::HEX;

            for (j = 0; j < PCI_CONF_BAR_MAX; j++)
              {
                ::cout << "     BAR=0x";
                ::cout << (int)pdev->bar[j];
                ::cout << "\n";
              }
          }

      }

    return;
  }
#endif

PciEnumerator::PciEnumerator()
{
  ::cout << "PciEnumerator() Constructor......\n";
}

PciEnumerator::~PciEnumerator()
{
  ::cout << "PciEnumerator() Destructor......\n";
}

/**
 *
 */
void
PciEnumerator::entry(void *pl)
{
  kernel::sys::sched::task::Task *pci_tsk;
  __scanPci();

#if 0
  pci_tsk =
  ::hwplatform.taskFactory.newTask(
      run_ixgbe,
      ::kernel::sys::sched::task::Task::KERNEL);

  ::hwplatform.eventController << kernel::irq::EventController::OFF;
  if (::hwplatform.scheduler)
    {
      *::hwplatform.scheduler += pci_tsk;
    }
  ::hwplatform.eventController << kernel::irq::EventController::ON;
#endif

  for (;;)
    {
    }

  return;
}

/**
 *
 */
::util::IRunnable*
PciEnumerator::newInstance(util::Capabilities& capabilities)
{
  PciEnumerator *pbus = new PciEnumerator();
  return pbus;
}

/**
 *
 */
void
PciEnumerator::onEvent(kernel::irq::Event *event)
{
  ::hwplatform.console << "[ PciEnumerator ] onEvent()\n";
}

/**
 *
 */
::util::IRunnable&
PciEnumerator::operator <<(kernel::cmd::CMD cmd)
{
  switch (cmd)
    {
  case kernel::cmd::INIT:
    break;
  default:
    break;
    }
  return *this;
}

/**
 *
 */
::util::Capabilities
PciEnumerator::getCaps(void)
{
  return caps;
}

/**
 *
 */
void
PciEnumerator::__scanPci(void)
{
  int conf_addr, conf_data;
  int busdev;
  int busnum;
  int devnum;
  int barnum;
  int pcidat;
  int header;
  int vendor;
  int device;
  int classc;
  int fun, funcs = 1;
  int busdevfun;
  int ndevs = 0;
  int bar0;
  unsigned char irq;

  // ::cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n";
  iobase = ::syscalls::remap(CONFIG_PCI_BASE, 0x1000);
  // ::cout << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << (int)iobase << "\n";

  ::hwplatform.eventController << kernel::irq::EventController::OFF;
  ::cout << kernel::Console::HEX;

  for (busnum = 0; busnum < (1 << 8); busnum++)
    {

      for (devnum = 0; devnum < (1 << 5); devnum++)
        {

          /* FIXME buffer overflow ? */
          if (ndevs >= MAX_PCI_DEV)
            break;

          busdev = (busnum << 16) | (devnum << 11) | (1 << 31);
          __outl(busdev, (int) iobase + PCI_CONF_ADR);
          pcidat = __inl((int) iobase + PCI_CONF_DATA);

          vendor = pcidat & 0x0000FFFF;
          if (vendor == PCI_NODEV)
            continue;

          device = pcidat >> 16;
          __outl(busdev | PCI_CONF_HDR_TYPEREG, (int) iobase + PCI_CONF_ADR);
          header = (__inl((int) iobase + PCI_CONF_DATA) >> 16) & 0xFF;

          /*
           * Multifunction device!
           */
          if (header & PCI_CONF_HDR_MULTIFUNC)
            {
              funcs = PCI_DEV_MAX_FUNCS;
            }

          /*
           * Enumerate each function
           */
          for (fun = 0; fun < funcs; fun++)
            {

              busdevfun = busdev | (fun << 8);
              __outl(busdevfun, (int) iobase + PCI_CONF_ADR);
              pci_dev_list[ndevs].r0.raw_r0 = __inl((int) iobase
                  + PCI_CONF_DATA);

              if (pci_dev_list[ndevs].r0.struct_r0.vendor_id == PCI_NODEV)
                continue;

              pci_dev_list[ndevs].bus_num = busnum;
              pci_dev_list[ndevs].dev_num = devnum;
              pci_dev_list[ndevs].func_num = fun;

              /* read device class */
              __outl(busdevfun | PCI_CONF_HDR_CLASSREG, (int) iobase
                  + PCI_CONF_ADR);
              pci_dev_list[ndevs].r2.raw_r2 = __inl((int) iobase
                  + PCI_CONF_DATA);

              /* Read IRQ */
              __outl(busdevfun | PCI_CONF_HDR_IRQ, (int) iobase + PCI_CONF_ADR);
              pci_dev_list[ndevs].interrupts.irq_reg = __inl((int) iobase
                  + PCI_CONF_DATA);

              /* Print out device info */
              ::cout << (int) busnum << ":" << (int) devnum << ".";
              ::cout << (int) fun << " Vend[0x"
                  << (int) pci_dev_list[ndevs].r0.struct_r0.vendor_id;
              ::cout << "] Dev[0x"
                  << (int) pci_dev_list[ndevs].r0.struct_r0.dev_id;
              ::cout << "] DevClass[0x"
                  << (int) pci_dev_list[ndevs].r2.struct_r2.classcode;
              ::cout << "] [rev."
                  << (int) pci_dev_list[ndevs].r2.struct_r2.rev_id;
              ::cout << "]\n";

              /* read bars */
              for (barnum = 0; barnum < PCI_CONF_BAR_MAX; barnum++)
                {
                  __outl(busdevfun | (PCI_CONF_HDR_BAR0 + (barnum << 2)),
                      (int) iobase + PCI_CONF_ADR);
                  pci_dev_list[ndevs].bar[barnum] = __inl((int) iobase
                      + PCI_CONF_DATA);
                  ::cout << "BAR=0x" << (int) pci_dev_list[ndevs].bar[barnum]
                      << "\n";
                }

              ndevs++;
            }
        }
    }

  scan_complete:

  ::hwplatform.eventController << kernel::irq::EventController::ON;
}

void
PciEnumerator::__setMaster(struct pci_dev *pdev)
{
  int busdev;
  busdev = (pdev->bus_num << 16) | (pdev->dev_num << 11) | (1 << 31);
  __outl(busdev + PCI_COMMAND, (int) iobase + PCI_CONF_ADR);
  __outw(PCI_COMMAND_MASTER, (int) iobase + PCI_CONF_ADR);
}

util::Capabilities PciEnumerator::caps
=
  { tags: "pci,bus,device,enumerate"};

imodule_init(pciEnumeratorInit,PciEnumerator::newInstance)
